﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using NUnit.Framework;
using NUnit.Framework.Constraints;
using TimScripts.BF4Rcon.Frostbite;

namespace TimScripts.BF4Rcon.Test
{
    public class RconEventManagerTests
    {
        RconEventManager Events;
        PlayerRconClient Client;
        TestEventProvider EventProvider;

        [SetUp]
        public void SetUp()
        {
            Client = new PlayerRconClient();
            EventProvider = new TestEventProvider();
            Events = new RconEventManager();

            Events.AddEventProvider(EventProvider, Client);
        }

        [Test]
        public void PlayerAuthenticated()
        {
            string playerName = "fooman";

            Client.Players.Add(playerName);

            Events.PlayerAuthenticated += (sender, e) =>
                {
                    CheckPlayer(playerName, e.Player);
                };

            EventProvider.Raise("player.onAuthenticated", playerName);
        }

        [Test]
        public void PlayerLeave()
        {
            string playerName = "foo";
            string propName = "woo";
            PlayerInfoBlockCreator blockCreator = new PlayerInfoBlockCreator();
            List<string> playerInfoBlock;

            playerInfoBlock = blockCreator.AddPlayers(playerName).AddProperties("name", propName).CreatePlayerInfoBlock(playerName);

            Events.PlayerLeave += (sender, e) =>
                {
                    Assert.AreEqual(playerName, e.Player.Name);

                    Assert.AreEqual(string.Empty, e.Player[propName]);
                };

            EventProvider.Raise("player.onLeave", playerInfoBlock.ToArray());
        }

        [Test]
        public void PlayerSpawn()
        {
            int teamId = 0;
            string playerName = "foo";

            Client.Players.Add(playerName);

            Events.PlayerSpawn += (sender, e) =>
                {
                    CheckPlayer(playerName, e.Player);
                };

            EventProvider.Raise("player.onSpawn", playerName, teamId.ToString());
        }

        [Test]
        public void PlayerKill()
        {
            string killerName = "killer";
            string victimName = "victim";
            bool headshot = true;
            string weapon = "nerf gun";

            Client.Players.Add(killerName);
            Client.Players.Add(victimName);

            Events.PlayerKill += (sender, e) =>
                {
                    CheckPlayer(killerName, e.Killer);
                    CheckPlayer(victimName, e.Victim);
                    Assert.AreEqual(weapon, e.Weapon);
                    Assert.AreEqual(headshot, e.IsHeadshot);
                };

            EventProvider.Raise("player.onKill", killerName, victimName, weapon, headshot.ToString());
        }

        [Test]
        public void PlayerChatSquad()
        {
            string playerName = "foo";
            string message = "barbaz";
            int team = 1;
            int squad = 1;

            Client.Players.Add(playerName);

            Events.PlayerChat += (sender, e) =>
                {
                    CheckPlayer(playerName, e.Player);
                    Assert.AreEqual(message, e.Message);
                    Assert.AreEqual(PlayerSubsetType.Squad, e.Target.SubsetType);
                    Assert.AreEqual(team, e.Target.TeamId);
                    Assert.AreEqual(squad, e.Target.SquadId);
                };

            EventProvider.Raise("player.onChat", playerName, message, "squad", team.ToString(), squad.ToString());
        }

        [Test]
        public void PlayerChatPlayer()
        {
            string playerName = "foo";
            string targetName = "target";
            string message = "barbaz";

            Client.Players.Add(playerName);
            Client.Players.Add(targetName);

            Events.PlayerChat += (sender, e) =>
            {
                CheckPlayer(playerName, e.Player);
                Assert.AreEqual(message, e.Message);
                Assert.AreEqual(PlayerSubsetType.Player, e.Target.SubsetType);
                CheckPlayer(targetName, e.Target.Player);
            };

            EventProvider.Raise("player.onChat", playerName, message, "player", targetName);
        }

        [Test]
        public void PunkBusterMessage()
        {
            string message = "foo\r\nwat\r\n";

            Events.PunkBusterMessage += (sender, e) =>
                {
                    Assert.AreEqual(message, e.Message);
                };

            EventProvider.Raise("punkBuster.onMessage", message);
        }

        [Test]
        public void MaxPlayerCountChange()
        {
            int count = 1337;

            Events.MaxPlayerCountChange += (sender, e) =>
                {
                    Assert.AreEqual(count, e.PlayerCount);
                };

            EventProvider.Raise("server.MaxPlayerCountChange", count.ToString());
        }

        [Test]
        public void LevelLoad()
        {
            Map map = new Map(MapName.FloodZone, GameMode.Defuse);
            string mapName = map.RawMapName;
            string modeName = map.RawModeName;
            int roundsPlayed = 13;
            int totalRounds = 37;

            Events.LevelLoad += (sender, e) =>
            {
                Assert.AreEqual(map.MapName, e.Map.MapName);
                Assert.AreEqual(map.GameMode, e.Map.GameMode);
                Assert.AreEqual(roundsPlayed, e.RoundsPlayed);
                Assert.AreEqual(totalRounds, e.TotalRounds);
            };

            EventProvider.Raise("server.onLevelLoaded", mapName, modeName, roundsPlayed.ToString(), totalRounds.ToString());
        }

        [Test]
        public void RoundOver()
        {
            List<string> teamScoresData = new List<string>();
            HashSet<string> players = new HashSet<string>(StringComparer.InvariantCulture) { "player1", "player2" };
            int[] teamScores = { 1, 2, 3 };
            int targetScore = 1337;
            int winningTeam = 9001;
            PlayerInfoBlockCreator playerData = new PlayerInfoBlockCreator();

            Events.RoundOver += (sender, e) =>
                {
                    Assert.That(e.TeamScores, Is.EquivalentTo(teamScores));

                    Assert.AreEqual(players.Count, e.Players.Count);
                    Assert.True(e.Players.All(p => players.Contains(p.Name) && p["awesomeness"].Equals(string.Empty)));

                    Assert.AreEqual(targetScore, e.TargetScore);

                    Assert.AreEqual(winningTeam, e.WinningTeamId);
                };

            teamScoresData.Add(teamScores.Length.ToString());
            teamScoresData.AddRange(teamScores.Select(s => s.ToString()));
            teamScoresData.Add(targetScore.ToString());
            EventProvider.Raise("server.onRoundOverTeamScores", teamScoresData.ToArray());

            EventProvider.Raise("server.onRoundOver", winningTeam.ToString());

            playerData.AddPlayers(players.ToArray()).AddProperties("name", "awesomeness");
            EventProvider.Raise("server.onRoundOverPlayers", playerData.CreatePlayerInfoBlock().ToArray());
        }

        [Test]
        public void SenderIsEventProvider()
        {
            string playerName = "fooman";

            Client.Players.Add(playerName);

            Events.PlayerAuthenticated += (sender, e) =>
                {
                    Assert.AreSame(EventProvider, sender);
                };

            EventProvider.Raise("player.onAuthenticated", playerName);
        }

        private static void CheckPlayer(string playerName, Player player)
        {
            Assert.AreEqual(playerName, player.Name);

            Assert.AreEqual(string.Empty, player["awesomeness"]);
        }

        private class TestEventProvider : IRconEventProvider
        {
            public event EventHandler<RconEventArgs> RconEvent;

            public void Raise(string eventName, params string[] eventData)
            {
                if (RconEvent != null)
                    RconEvent(this, new RconEventArgs(eventName, eventData));
            }
        }
    }
}
